#include "stdafx.h"
#include "cppflibinc.h"
#include "TestMiscFilePath.h"

using namespace cppflib;
using namespace cppflib::util;
using namespace cppflib::threading;
using namespace cppflib::lang;
using namespace cppflib::os;
using namespace cppflib::io;
using namespace cppflib::collections;
using namespace cppflib::exceptions;
using namespace cppflib::security::cert;
using namespace cppflib::net;
using namespace cppflib::net::ssl;
using namespace cppflib::net::http;
using namespace cppflib::net::http::message;

static void PrintHeaders(HttpMessage &msg)
{
   for (int i = 0; i < static_cast<int>(msg.GetHeaderCount()); ++i) {
      CFString *pName, *pVal;
      msg.GetHeader(i, pName, pVal);
      debug::Print(debug::DL_INFO, _S("%s: %s\n"), static_cast<pcwstr_t>(*pName), static_cast<pcwstr_t>(*pVal));
   }
}

static void PrintRequestResponseHeaders(HttpRequest &req, HttpResponse &res)
{
   debug::Print(debug::DL_INFO, _S("===== request headers =====\n"));
   CFString url;
   req.GetURL(url);
   debug::Print(debug::DL_INFO, _S("method: %s, url = %s\n"), Enum::GetString<http::httpmethod_t>(req.GetMethod()), static_cast<pcwstr_t>(url));
   if (req.HasUrlParams()) {
      NameValueList keyValList;
      req.GetUrlParams(keyValList);
      for (int i =0 ; i < static_cast<int>(keyValList.GetCount()); ++i) 
         debug::Print(debug::DL_INFO, _S("%s = %s\n"), static_cast<pcwstr_t>(*keyValList.GetKey(i)), static_cast<pcwstr_t>(*keyValList.GetVal(i)));
   }
   PrintHeaders(req);
   debug::Print(debug::DL_INFO, _S("===== response headers ======\n"));
   debug::Print(debug::DL_INFO, _S("ver = %s, code = %d, reason = %s\n"), static_cast<pcwstr_t>(res.GetHttpVersion()),
                                                                          res.GetResponseCode(),
                                                                          static_cast<pcwstr_t>(res.GetResponseReason())); 
   PrintHeaders(res);
}

static void PrintResponseContent(IHttpBody *pBody)
{
   if (!pBody)
      return;

   sz_t contentLength = static_cast<sz_t>(pBody->GetContentLength());
   debug::Print(debug::DL_INFO, _S("body length = %u\n"), contentLength);
   ArrayByte buffer(128);
   
   while (true) {
      if (!pBody->IsCompressed() && contentLength > 0) {
         if (pBody->GetBytesReceived() >= contentLength)
            break;
      }

      sz_t nrRecv = pBody->Read(buffer);
      if (nrRecv == 0)
         break;

      CFString s(reinterpret_cast<const char*>(buffer.GetRawPtr()), static_cast<int>(nrRecv));
      s.Replace(_S("%"), _S("%%%%"));
      debug::PlainPrint(s);      
   }
   debug::PlainPrint(_S("\n"));
   debug::Print(debug::DL_INFO, _S("length recved = %u\n"), pBody->GetBytesReceived());
}

static void GetResponseContentSaveFile(IHttpBody *pBody, bool isSaveToFile,int index)
{
   if (!pBody)
      return;

   CFString fileName;
   fileName.Format(_S("hello_%d.txt"), index); 
   
   sz_t contentLength = static_cast<sz_t>(pBody->GetContentLength());
   debug::Print(debug::DL_INFO, _S("body length = %u\n"), contentLength);
   FileStream *fs = isSaveToFile ? new FileStream(fileName, FileStream::FA_WRITE) : NULL;
   ArrayByte buffer(128);

   while (true) {
      if (!pBody->IsCompressed() && contentLength > 0) {
         if (pBody->GetBytesReceived() >= contentLength)
            break;
      }

      sz_t nrRecv = pBody->Read(buffer);
      if (nrRecv == 0)
         break;

      if (fs)
         fs->Write(buffer, 0, static_cast<int>(nrRecv));
   }

   debug::Print(debug::DL_INFO, _S("length recved = %u\n"), pBody->GetBytesReceived());
   if (fs)
      cppmempool::Release(fs);
}

static void TestHttpGet()
{
   cppmempool::MemPoolWrap localPool;

   HttpRequest *pReq = new HttpRequest(http::GET);
   pReq->SetUrl(_S("http://127.0.0.1:8080/testphp/testpage.php"));
   //pReq->SetHeader(_S("Expect"), _S("100-continue"));
   //pReq->SetHeader(_S("User-Agent"), _S("Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.168 Safari/535.19"));

   HttpParams *pParams = new HttpParams();
   HttpClient *pClient = new HttpClient(pParams);
   HttpResponse *pRes = pClient->IssueRequest(pReq);

   PrintRequestResponseHeaders(*pReq, *pRes);

   IHttpBody *pBody = pRes->GetHttpBody();
   GetResponseContentSaveFile(pBody, false, 0);
}

static void TestHttpUpload()
{
   cppmempool::MemPoolWrap localPool;

   NameValueList nvList;
   nvList.AddTail(new CFString(_S("name1")), new CFString(_S("val ue1?&*")));
   nvList.AddTail(new CFString(_S("name2")), new CFString(_S("~~~value2~~~")));
   nvList.AddTail(new CFString(_S("name3")), new CFString(_S("!@#value3$%^")));
   nvList.AddTail(new CFString(_S("name4")), new CFString(_S("(-+=value4*_<>,.;)")));
      
   HttpRequest *pReq = new HttpRequest(http::POST);
   pReq->SetUrl(_S("http://127.0.0.1:8080/testphp/testformdata.php"));
   pReq->SetHttpBody(new UrlEncodedBody(nvList));

   HttpParams *pParams = new HttpParams();
   HttpClient *pClient = new HttpClient(pParams);
   HttpResponse *pRes = pClient->IssueRequest(pReq);

   PrintRequestResponseHeaders(*pReq, *pRes);

   IHttpBody *pBody = pRes->GetHttpBody();
   PrintResponseContent(pBody);
}

static void TestHttpUpload2()
{
   cppmempool::MemPoolWrap localPool;

   HttpRequest *pReq = new HttpRequest(http::POST);
   pReq->SetUrl(_S("http://127.0.0.1:8080/testphp/testuploadstr.php"));
   CFString *str = new CFString(_S("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"));
   pReq->SetHttpBody(new StringBody(str));
   pReq->GetHttpBody()->SetChunked(true);
   pReq->SetHeader(_S("Expect"), _S("100-continue"));

   HttpParams *pParams = new HttpParams();
   HttpClient *pClient = new HttpClient(pParams);
   HttpResponse *pRes = pClient->IssueRequest(pReq);

   PrintRequestResponseHeaders(*pReq, *pRes);


   IHttpBody *pBody = pRes->GetHttpBody();
   PrintResponseContent(pBody);
}

static void TestHttpGetWithProxy()
{
   cppmempool::MemPoolWrap localPool;

   HttpRequest *pReq = new HttpRequest(http::GET);
   pReq->SetUrl(_S("http://www.google.com.hk/"));
   pReq->SetHeader(_S("Accept-Encoding"), _S("gzip, deflate")); 
   
   HttpParams *pParams = new HttpParams();
   //pParams->SetParam(HttpParams::P_PROXY, new NameValuePair(new CFString(_S("10.11.12.179")), new CFString(_S("8080"))));
   HttpClient *pClient = new HttpClient(pParams);
   HttpResponse *pRes = pClient->IssueRequest(pReq);

   PrintRequestResponseHeaders(*pReq, *pRes);

   IHttpBody *pBody = pRes->GetHttpBody();
   GetResponseContentSaveFile(pBody, false, 0);
}

class MySSLPeerCertCallback : public ISSLPeerCertCallback
{
public:
    MySSLPeerCertCallback()
    {
    }

    ~MySSLPeerCertCallback()
    {
    }

    virtual void ReadPeerCert(bool isCertOK, security::cert::X509Certificate &x509Cert)
    {
/*
       debug::Print(_S("============================\n"));
       debug::Print(debug::DL_DEBUG, _S("%s\n%s\n"), static_cast<pcwstr_t>(x509Cert.GetIssuerName()), 
                                                     static_cast<pcwstr_t>(x509Cert.GetSubjectName()));
       debug::Print(_S("============================\n"));
*/
    }

    /**
     *  Called when connection is established with peer
     */
    virtual void ReadPeerCertAfterConnection(security::cert::X509Certificate &x509Cert)
    {
/*
       debug::Print(_S("============================\n"));
       debug::Print(debug::DL_DEBUG, _S("%s\n%s\n"), static_cast<pcwstr_t>(x509Cert.GetIssuerName()), 
                                                     static_cast<pcwstr_t>(x509Cert.GetSubjectName()));
       debug::Print(_S("============================\n"));
*/
    }
};

static void TestHttpSSLGet()
{
   cppmempool::MemPoolWrap localPool;

   HttpRequest *pReq = new HttpRequest(http::GET);
   pReq->SetUrl(_S("https://127.0.0.1/modeChange.html"));
   
   HttpParams *pParams = new HttpParams();
   SSLSocketFactory *pSockFactory = new SSLSocketFactory(CFString(ROOTCERT), new MySSLPeerCertCallback());

   pParams->SetParam(HttpParams::P_SOCKETFACTORY, pSockFactory);

   HttpClient *pClient = new HttpClient(pParams);
   HttpResponse *pRes = pClient->IssueRequest(pReq);

   PrintRequestResponseHeaders(*pReq, *pRes);

   IHttpBody *pBody = pRes->GetHttpBody();
   PrintResponseContent(pBody);
}




static void TestHttpSSLMultiGetOneByOne()
{
   cppmempool::MemPoolWrap localPool;

   pcwstr_t urlArray[] = { _S("https://www.google.com.hk/"),
                           _S("https://www.google.com.hk/intl/zh-TW/ads/"),
                           _S("https://www.google.com.hk/intl/zh-TW/policies/"),
                           NULL};


   HttpParams *pParams = new HttpParams();

   SSLSocketFactory *pSockFactory = new SSLSocketFactory(CFString(REALWORLD_ROOTCERT), new MySSLPeerCertCallback());
   pParams->SetParam(HttpParams::P_SOCKETFACTORY, pSockFactory);

   HttpClient *pClient = new HttpClient(pParams);
   for (int i = 0; urlArray[i]; ++i) {

      HttpRequest *pReq = new HttpRequest((i == 1) ? http::HEAD : http::GET);
      pReq->SetHeader(_S("Connection"), _S("Keep-alive"));
      pReq->SetUrl(urlArray[i]);
      HttpResponse *pRes = pClient->IssueRequest(pReq);
      PrintRequestResponseHeaders(*pReq, *pRes);
      IHttpBody *pBody = pRes->GetHttpBody();
      GetResponseContentSaveFile(pBody, true, i);
      debug::Print(_S(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"));
      cppflib::threading::Thread::Sleep(1000);
   }
}

static void TestHttpSSLPipelinedRequests()
{
   debug::Print(_S("Testing Pipelined Http ...\n"));

   cppmempool::MemPoolWrap localPool;

   pcwstr_t urlArray[] = { _S("http://www.google.com.hk/"),
                           _S("http://www.google.com.hk/intl/zh-TW/ads/"),
                           _S("http://www.google.com.hk/intl/zh-TW/policies/"),
                           NULL};

   HttpParams *pParams = new HttpParams();
   SSLSocketFactory *pSockFactory = new SSLSocketFactory(CFString(REALWORLD_ROOTCERT), new MySSLPeerCertCallback());
   pParams->SetParam(HttpParams::P_SOCKETFACTORY, pSockFactory);

   HttpClient *pClient = new HttpClient(pParams);
   ArrayCppObjT<HttpRequest> requests(sizeof(urlArray) / sizeof(pcwstr_t) - 1);
   for (int i = 0; urlArray[i]; ++i) {
      HttpRequest *pReq = new HttpRequest(http::GET);
      pReq->SetHeader(_S("Connection"), _S("Keep-alive"));
      pReq->SetUrl(urlArray[i]);
      requests.Set(i, pReq);
   }

   debug::Print(debug::DL_DEBUG, _S("Sending Pipelined requests -- total: %u\n"), requests.GetSize());
   IPipelinedResponses *pMulRes = pClient->IssuePipelinedRequests(requests);
   for (int i = 0; HttpResponse * pRes = pMulRes->GetResponse(); ++i) {
      debug::Print(debug::DL_DEBUG, _S(">>>>>>>>> Pipelined response (%d) <<<<<<<<<<\n"), i + 1);
      PrintRequestResponseHeaders(*requests.Get(i), *pRes);
      IHttpBody *pBody = pRes->GetHttpBody();
      GetResponseContentSaveFile(pBody, true, i);
   }
   ///cppflib::threading::Thread::Sleep(1000);
   
}


/**************************** HttpServer ******************************/ 

static void __ServeClientFunc(HttpServerEndPoint *pEndPoint)
{
   
   while(true) {
      cppmempool::MemPoolWrap localPool;

      HttpRequest *pReq = pEndPoint->GetRequest();
      CFString reqPath;
      pReq->GetReqPath(reqPath);
     

      CFString bodyString;
      if (pReq->HasUrlParams()) {       // any URL parameters ?
         NameValueList keyValList;
         pReq->GetUrlParams(keyValList);
         for (int i = 0 ; i < static_cast<int>(keyValList.GetCount()); ++i) 
            bodyString.AppendFormat(_S("%s = %s<br/>"), static_cast<pcwstr_t>(*keyValList.GetKey(i)), 
                                                        static_cast<pcwstr_t>(*keyValList.GetVal(i)));
      }

      if (pReq->HasHttpBody()) {        // any post body ?
         IHttpBody *pBody = pReq->GetHttpBody();
         ArrayByte buffer(256);
         
         while (true) {
            int nrByteRead = pBody->Read(buffer, 0, static_cast<int>(buffer.GetSize() - 1));
            if (nrByteRead <= 0)
               break;

            buffer[nrByteRead] = 0;
            bodyString.AppendFormat(_S("%S"), reinterpret_cast<char*>(buffer.GetRawPtr()));
         }
      }


      HttpResponse *pRes = NULL;
      if (reqPath.EndsWith(_S(".html")))
         pRes = new HttpResponse(http::HTTP_VER_1_1, 200, _S("OK"));
      else
         pRes = new HttpResponse(http::HTTP_VER_1_1, 404, _S("Not found"));
      //pRes->SetHeader(_S("Connection"), _S("close"));

            
      CFString * bStr = new CFString();
      bStr->Format(_S("<html><body>What you are requesting:<br/>%s<br/>"), static_cast<pcwstr_t>(reqPath));
      if (!bodyString.IsEmpty())
         bStr->AppendFormat(_S("posted string:<br/>%s<br/>"), static_cast<pcwstr_t>(bodyString));
      bStr->AppendFormat(_S("</body></html>"));

      StringBody *pBody = new StringBody(bStr);
      pBody->SetContentType(_S("text/html"));
      pRes->SetHttpBody(pBody);
      pEndPoint->SendResponse(*pRes);
      PrintRequestResponseHeaders(*pReq, *pRes);
   }

}

static void __AutoServeClientFunc(HttpServerEndPoint *pEndPoint)
{
   
   while(true) {
      cppmempool::MemPoolWrap localPool;

      HttpRequest *pReq = pEndPoint->GetRequest();
      CFString reqPath;
      pReq->GetReqPath(reqPath);

      CFString *bodyString = new CFString(_S("<html><body>"));
      if (pReq->HasUrlParams()) {       // any URL parameters ?
         NameValueList keyValList;
         pReq->GetUrlParams(keyValList);
         for (int i = 0 ; i < static_cast<int>(keyValList.GetCount()); ++i) 
            bodyString->AppendFormat(_S("[%s:%s]"), static_cast<pcwstr_t>(*keyValList.GetKey(i)), 
                                                 static_cast<pcwstr_t>(*keyValList.GetVal(i)));
      }

      if (pReq->HasHttpBody()) {        // any post body ?
         IHttpBody *pBody = pReq->GetHttpBody();
         ArrayByte buffer(256);
         
         while (true) {
            int nrByteRead = pBody->Read(buffer, 0, static_cast<int>(buffer.GetSize() - 1));
            if (nrByteRead <= 0)
               break;

            buffer[nrByteRead] = 0;
            bodyString->AppendFormat(_S("%S"), reinterpret_cast<char*>(buffer.GetRawPtr()));
         }
      }
      bodyString->AppendFormat(_S("</body></html>"));

      HttpResponse *pRes = NULL;
      if (reqPath.EndsWith(_S(".html")))
         pRes = new HttpResponse(http::HTTP_VER_1_1, 200, _S("OK"));
      else
         pRes = new HttpResponse(http::HTTP_VER_1_1, 404, _S("Not found"));

      StringBody *pBody = new StringBody(bodyString);
      pBody->SetContentType(_S("text/html"));
      pRes->SetHttpBody(pBody);
      pEndPoint->SendResponse(*pRes);
   }

}

static SyncLock * st_pCancelLock = NULL;
static HashtableT<Int64, WeakReferenceT<ICancelContext> > *st_pCancelTable = NULL;

static void AddCancelContext(ptr_t threadId, WeakReferenceT<ICancelContext> * pCancelContext)
{
   SyncWrap<SyncLock> lock(st_pCancelLock);
   st_pCancelTable->Set(new Int64(threadId), pCancelContext);
}

static void RemoveCancelContext(ptr_t threadId)
{
   SyncWrap<SyncLock> lock(st_pCancelLock);
   st_pCancelTable->Remove(Int64(threadId));
}

static void TurnOnAllCancelContext()
{
   SyncWrap<SyncLock> lock(st_pCancelLock);
   u64_t pos = 0;
   const Int64 *pKey = NULL;
   WeakReferenceT<ICancelContext> *pCancelContext = NULL;
   
   debug::Print(debug::DL_INFO, _S("Cancel context num = %u\n"), st_pCancelTable->GetCount());

   while (st_pCancelTable->EnumerateElement(pos, &pKey, &pCancelContext)) {
      ICancelContext *pPtr = pCancelContext->Ptr();
      if (pPtr)
         pPtr->CancelWaiting();
   }

}

static void __ThreadPoolFunc(ptrany_t pArg)
{
   cppmempool::MemPoolWrap memPool;

   HttpServerEndPoint *pEndPoint = static_cast<HttpServerEndPoint*>(pArg);
   cppmempool::AddToCurPool(pEndPoint);

   // add the this endpoint's cancel context to the hash table
   AddCancelContext(Thread::GetCurrentThreadId(), pEndPoint->GetCancelContext());

   try {
      //__ServeClientFunc(pEndPoint);
      __AutoServeClientFunc(pEndPoint);
   }
   catch (BaseException &e) {
      debug::Print(debug::DL_ERROR, _S("Server-client func exception occurs: err = %s, errno = %d\n"), 
                                       e.GetMsg(), e.GetErrCode());
   }

   // remove cancel context from the table
   RemoveCancelContext(Thread::GetCurrentThreadId());
}


class MyServerPrivateKeyCallback : public ISSLPrivateKeyPwdCallback
{
public:
    virtual void GetPwd(CFString &pwd)
    {
       pwd = "zxcv1234";  // fill in password for private key
    }
};


static int __ServerThread(ptr_t p)
{
   debug::Print(_S("Launching HttpServer ...\n"));   

   HttpServer *pServer = reinterpret_cast<HttpServer*>(p);
   cppmempool::AddToCurPool(pServer);

   pServer->StartServer();
   ArrayCppObjT<HttpServerEndPoint> endPointArray(pServer->GetListeningSocketCount());

   ThreadPool *pThreadPool = new ThreadPool(5);
   pThreadPool->StartAllThreads();
   ThreadPool::UserFunc *pUserFunc = new ThreadPool::UserFunc(&__ThreadPoolFunc);

   try {
       while (true) {
          cppmempool::MemPoolWrap localPool;

          int nrClients = pServer->GetIncomingClients(endPointArray);
          if (nrClients <= 0)
             continue;

          debug::Print(debug::DL_INFO, _S(">>>>>>>>>>> Incoming clients: %d <<<<<<<<<<\n"), nrClients);

          for (int i = 0 ;i < static_cast<int>(endPointArray.GetSize()); ++i) {
             HttpServerEndPoint *pEndPoint = endPointArray.Get(i);
             if (pEndPoint) {
                cppmempool::UnlinkFromPool(pEndPoint);
                pThreadPool->ExecuteOnIdleThread(pUserFunc, reinterpret_cast<ptrany_t>(pEndPoint));
             }
          }
          
          endPointArray.Clear();
       }
   }
   catch (BaseException &e) {
       debug::Print(debug::DL_ERROR, _S("error: %s, %d\n"), e.GetMsg(), e.GetErrCode());
   }

   pThreadPool->StopAllThreads();

   return 0;
}

static int __ServerThreadMain(ptr_t p)
{
   cppmempool::MemPoolWrap memPool;
   
   try {
      return __ServerThread(p);
   }
   catch (BaseException &e) {
      debug::Print(debug::DL_FATAL, _S("error = %s (%d)\n"), e.GetMsg(), e.GetErrCode());
      assert(false);
   }

   return 0;
}

PRIVATE const u16_t NORMAL_HTTP_PORT = 1234;
PRIVATE const u16_t SECURE_HTTPS_PORT = 1244;

static HttpServer * CreateServerObject()
{
   HttpParams *pParams = new HttpParams();
   
   ListCppObjT<VirtualHostInfo> * pVHInfoList = new ListCppObjT<VirtualHostInfo>();
   // normal HTTP on 1234 port with 1 virtual host
   pVHInfoList->AddTail(new VirtualHostInfo(NORMAL_HTTP_PORT, CFString::Empty(), CFString(_S("www.myserver.com"))));

   // HTTPS on 1244 port with 2 virtual hosts 
   MyServerPrivateKeyCallback *pPwdCb = new MyServerPrivateKeyCallback();
   SSLVirtualHostInfo *pSSLVHInfo = new SSLVirtualHostInfo(SECURE_HTTPS_PORT, CFString::Empty(), 
                              CFString(_S("www.myserver.com")), CFString(SERVERCERT), pPwdCb, NULL, false);
   pSSLVHInfo->AddVirtualHost(CFString(_S("www.anotherserver.com")), CFString(ANOTHERSERVERCERT), pPwdCb, NULL, false);

   pVHInfoList->AddTail(pSSLVHInfo);

   pParams->SetParam(HttpParams::P_SOCKETFACTORY, new SSLServerSocketFactory(CFString(ROOTCERT), pVHInfoList));
   pParams->SetParam(HttpParams::P_SOTIMEOUT, 0x7fffffff);
   
   HttpServer *pServer = new HttpServer(pParams);
   cppmempool::UnlinkFromPool(pServer);

   return pServer;
}

static void CheckReceivedContent(IHttpBody *pBody, const CFString &chkString)
{
   assert(pBody);

   sz_t contentLength = static_cast<sz_t>(pBody->GetContentLength());
   ArrayByte buffer(128);
   BinBuf binBuf(256);
   
   while (true) {
      if (!pBody->IsCompressed() && contentLength > 0) {
         if (pBody->GetBytesReceived() >= contentLength)
            break;
      }

      sz_t nrRecv = pBody->Read(buffer);
      if (nrRecv == 0)
         break;

      binBuf.AppendData(buffer.GetRawPtr(), nrRecv, true);
   }

   if (chkString.StartsWith(_S("file://"))) {
      // check FileBody
      const char *prefix = "<html><body>";
      const char *suffix = "</body></html>";

      char * pBuf = reinterpret_cast<char*>(binBuf.GetRawBuf());
      for (int i = 0; prefix[i]; ++i)
         assert(prefix[i] == *pBuf++);
      
      CFString filePath = chkString.Mid(7);
      FileStream fStream(filePath, FileStream::FA_READONLY);
      while (true) {
         int c = fStream.Read();
         if (c < 0) break;
         assert(*pBuf++ == static_cast<char>(c));
      }

      for (int i = 0; suffix[i]; ++i)
         assert(suffix[i] == *pBuf++);
   }
   else {
      CFString s(reinterpret_cast<const char*>(binBuf.GetRawBuf()), static_cast<int>(binBuf.GetValidLength()));
      assert(s == chkString);
   }
}

/************** Client requests ***************/

PRIVATE bool st_isSSL = false;

static void GetTestUrl(CFString &url)
{
   if (st_isSSL)
      url.Format(_S("https://127.0.0.1:%d/hello.html"), SECURE_HTTPS_PORT);
   else 
      url.Format(_S("http://127.0.0.1:%d/hello.html"), NORMAL_HTTP_PORT);
}

static void GetTestRequest_1(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::GET);
   NameValueList keyValList;
   keyValList.AddTail(new CFString("didi=+0_$%&_"), new CFString("q1233456"));
   keyValList.AddTail(new CFString("didi"), new CFString("q16"));
   keyValList.AddTail(new CFString("(=)"), new CFString("###"));
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl, keyValList);

   chkString = _S("<html><body>[didi=+0_$%&_:q1233456][didi:q16][(=):###]</body></html>");
}

static void GetTestRequest_2(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::GET);
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   chkString = _S("<html><body></body></html>");
}

PRIVATE const char * pTestStr = "When a web page is requested via the Hypertext Transfer Protocol, the server locates a file in its file system based on the requested URL. This file may be a regular file or a program. In the second case, the server may (depending on its configuration) run the program, sending its output as the required page. The query string is a part of the URL which is passed to the program. Its use permits data to be passed from the HTTP client (often a web browser) to the program which generates the web page.";

/**
 *  Test StringBody
 */
static void GetTestRequest_3(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   pReq->SetHeader(_S("Expect"), _S("100-continue"));

   NameValueList keyValList;
   keyValList.AddTail(new CFString("hello"), new CFString("world"));
   keyValList.AddTail(new CFString("man"), new CFString("ok"));
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl, keyValList);

   CFString *bodyString = new CFString(pTestStr);
   StringBody *pBody = new StringBody(bodyString);
   pBody->SetContentType(_S("text/plain"));
   pReq->SetHttpBody(pBody);

   chkString.Format(_S("<html><body>[hello:world][man:ok]%S</body></html>"), pTestStr);
}

/**
 *  Test ByteStreamBody(BinBuf)
 */
static void GetTestRequest_4(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   pReq->SetHeader(_S("Expect"), _S("100-continue"));

   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   BinBuf *pBinBuf = new BinBuf();
   pBinBuf->AppendData(reinterpret_cast<const u8_t*>(pTestStr), CFString::Strlen(pTestStr));
   ByteStreamBody * pBody = new ByteStreamBody(pBinBuf);
   pBody->SetContentType(_S("text/plain"));
   pReq->SetHttpBody(pBody);

   chkString.Format(_S("<html><body>%S</body></html>"), pTestStr);
}

/**
 *  Test ByteStreamBody(MemoryStream)
 */
static void GetTestRequest_5(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   const char * pTraStr = pTestStr;
   MemoryStream *pMemStream = new MemoryStream();
   while (*pTraStr)    
      pMemStream->Write(*pTraStr++);
   ByteStreamBody * pBody = new ByteStreamBody(pMemStream);
   pBody->SetContentType(_S("text/plain"));
   pReq->SetHttpBody(pBody);

   chkString.Format(_S("<html><body>%S</body></html>"), pTestStr);
}

/**
 *  Test ByteStreamBody(ArrayByte)
 */
static void GetTestRequest_6(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   const char * pTraStr = pTestStr;
   ArrayByte *pArray = new ArrayByte(CFString::Strlen(pTestStr));
   for (int i = 0; pTraStr[i]; ++i)    
      (*pArray)[i] = pTraStr[i];
   ByteStreamBody * pBody = new ByteStreamBody(pArray);
   pBody->SetContentType(_S("text/plain"));
   pReq->SetHttpBody(pBody);

   chkString.Format(_S("<html><body>%S</body></html>"), pTestStr);
}

/**
 *  Test UrlEncodedBody
 */
static void GetTestRequest_7(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   NameValueList keyValList;
   keyValList.AddTail(new CFString("hello"), new CFString("world"));
   keyValList.AddTail(new CFString("man"), new CFString("ok"));
   keyValList.AddTail(new CFString("didi=+0_$%&_"), new CFString("q1233456"));
   keyValList.AddTail(new CFString("didi"), new CFString("q16"));
   keyValList.AddTail(new CFString("(=)"), new CFString("###"));

   UrlEncodedBody * pBody = new UrlEncodedBody(keyValList);
   pBody->SetContentType(_S("text/plain"));
   pReq->SetHttpBody(pBody);

   chkString = _S("<html><body>hello=world&man=ok&didi%3d%2b0_%24%25%26_=q1233456&didi=q16&%28%3d%29=%23%23%23</body></html>");
}

/**
 *  Test FileBody
 */
static void GetTestRequest_8(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   pcwstr_t fileName = XML_PATH_PREFIX _S("hello_big_1.xml");
   FileBody * pBody = new FileBody(fileName);
   pBody->SetContentType(_S("text/plain"));
   pBody->SetCompressed(true);
   pReq->SetHttpBody(pBody);

   chkString.Format(_S("file://%s"), fileName);
}

/**
 *  Test FileBody chunked
 */
static void GetTestRequest_9(HttpRequest *& pReq, CFString & chkString)
{
   pReq = new HttpRequest(http::POST);
   pReq->SetHeader(_S("Expect"), _S("100-continue"));

   CFString remoteUrl;
   GetTestUrl(remoteUrl);
   pReq->SetUrl(remoteUrl);

   pcwstr_t fileName = XML_PATH_PREFIX _S("hello_big_3.xml");
   FileBody * pBody = new FileBody(fileName);
   pBody->SetContentType(_S("text/plain"));
   pBody->SetChunked(true);
   pBody->SetCompressed(true);
   pReq->SetHttpBody(pBody);
    

   chkString.Format(_S("file://%s"), fileName);
}

static void TestClientRequest()
{
   cppmempool::MemPoolWrap localPool;

   FuncPtr2T<void, HttpRequest*&, CFString&> *testReqArray[] = 
                              {
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_1),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_2),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_3),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_4),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_5),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_6),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_7),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_8),
                                 new FuncPtr2T<void, HttpRequest*&, CFString&>(&GetTestRequest_9),
                                 NULL
                              };

   Thread::Sleep(1500);

   debug::Print(debug::DL_INFO, _S("Make request to HttpServer %s...\n"), st_isSSL ? _S("(SSL)") : _S(""));
   
   HttpParams *pParams = new HttpParams();
   SSLSocketFactory *pSockFactory = new SSLSocketFactory(CFString(ROOTCERT), NULL);
   pSockFactory->SetVerifyHostnameType(SSLSocketFactory::NOTVERIFY);
   pParams->SetParam(HttpParams::P_SOCKETFACTORY, pSockFactory);
   pParams->RemoveParam(HttpParams::P_PROXY); // remove any proxy

   
   HttpClient *pClient = new HttpClient(pParams);
   CFString chkString;

   for (int i = 0 ; testReqArray[i]; ++i) {

      debug::Print(debug::DL_DEBUG, _S("Testing sample (%d)\n"), i + 1);
      
      HttpRequest *pReq = NULL;      
      FuncPtr2T<void, HttpRequest*&, CFString&> *pFunc = testReqArray[i];
      (*pFunc)(pReq, chkString);
      assert(pReq);

      HttpResponse *pRes = pClient->IssueRequest(pReq);

      if (i == 0 && pReq->IsSSLRequired()) {
         debug::Print(debug::DL_DEBUG, _S("####### Take a peek of the cert #######\n")); // peek peer cert
         X509Certificate * pCert = pClient->GetPeerCertificate();
         assert(pCert != NULL);
         CFString commonName;
         assert(pCert->GetOneValueFromSubjectName(CFString("CN"), commonName));
         assert(commonName == _S("*.myserver.com"));
      }

      IHttpBody *pBody = pRes->GetHttpBody();

      CheckReceivedContent(pBody, chkString);

      Thread::Sleep(50);
   }
}

static void TestHttpClientServer()
{
   cppmempool::MemPoolWrap memPool;
   
   st_pCancelLock = new SyncLock();
   st_pCancelTable = new HashtableT<Int64, WeakReferenceT<ICancelContext> >();

   HttpServer *pServer = CreateServerObject();
   ICancelContext *pCancelServer = pServer->GetCancelContext();

   Thread a((ThreadEntryPoint(&__ServerThreadMain)));
   a.Start(reinterpret_cast<ptr_t>(pServer));

   try {
      TestClientRequest();
      st_isSSL = true;
      TestClientRequest();  // once more with SSL
   }
   catch (BaseException &e) {
      debug::Print(debug::DL_FATAL, _S("error = %s (%d)\n"), e.GetMsg(), e.GetErrCode());
      assert(false);
   }
   //getchar(); // wait for 'enter'

   pCancelServer->CancelWaiting();
   TurnOnAllCancelContext();
   
   a.Join();
}

static void TestHttpUtility()
{ 
   cppmempool::MemPoolWrap memPool;

   CFString ret;
   NameValueList keyValList;
   keyValList.AddTail(new CFString("didi=+0_$%&_"), new CFString("q1233456"));
   keyValList.AddTail(new CFString("didi"), new CFString("q16"));
   keyValList.AddTail(new CFString("(=)"), new CFString("###"));

   http::util::UrlEncodeNameValuePairs(keyValList, ret);
   assert(ret == _S("didi%3d%2b0_%24%25%26_=q1233456&didi=q16&%28%3d%29=%23%23%23")); 

   ret += _S("&kd%28k%3ddkd"); // add an incomplete name-value pair

   NameValueList keyValList_2;
   http::util::DecodeUrlEncodedNameValueString(ret, keyValList_2);
   assert(keyValList_2.GetCount() == 3);
   keyValList_2.Clear();

   ret += _S("=");
   http::util::DecodeUrlEncodedNameValueString(ret, keyValList_2);
   assert(keyValList_2.GetCount() == 3);

   for (int i = 0; i < static_cast<int>(keyValList.GetCount()); ++i) {
      assert(*keyValList.GetKey(i) == *keyValList_2.GetKey(i));
      assert(*keyValList.GetVal(i) == *keyValList_2.GetVal(i));
   }

}

static void TestHttpMain()
{
   try {
      //TestHttpGet();
      //TestHttpUpload();
      //TestHttpUpload2();
      //TestHttpGetWithProxy();
      //TestHttpSSLGet();
      //TestHttpSSLMultiGetOneByOne();
      //TestHttpSSLPipelinedRequests();
     TestHttpClientServer();
   }
   catch (BaseException &e) {
      debug::Print(debug::DL_FATAL, _S("error = %s (%d)\n"), e.GetMsg(), e.GetErrCode());
      assert(false);
   }
}


void TestHttp(void)
{
   debug::Print(_S("Testing Http ...\n"));
   TestHttpUtility();
   TestHttpMain();
}
